#include "defaults.h"
#include "nurbs.h"
#include "nurbssurfacerenderer.h"

NurbsSurfaceRenderer::NurbsSurfaceRenderer(NurbsSurfaceMesh* primitive) 
	: PrimitiveRenderer(primitive)
{
	positionAttribute = "position";

	for(size_t i = 0; i < primitive->SizeElements(); i++)
		element.push_back(static_cast<SNurbs*>(primitive->GetElement(i)));

	Init();
}

void TesselateNormal(SNurbs* element, boost::multi_array<Vec3n, 2>& data, const std::string& attribute) {
	const std::pair<Vec2n, Vec2n>& domain = element->Domain();
	const Vec2n& uDomain = domain.first;
	const Vec2n& vDomain = domain.second;

	const size_t n = data.shape()[0];
	const size_t m = data.shape()[1];

	for(size_t i = 0; i < n; i++) {
		nfloat u = 0.999999 * ((nfloat)i / (n - 1));
		u = u * (uDomain[1] - uDomain[0]) + uDomain[0];
		if(i == 0) u = uDomain[0] +  0.000001;
		for(size_t j = 0; j < m; j++) {							
			nfloat v = 0.999999 * ((nfloat)j / (m - 1));
			v = v * (vDomain[1] - vDomain[0]) + vDomain[0];
			if(j == 0) v = vDomain[0] + 0.000001;

			data[i][j] = element->Normal(math::V2n(u, v), attribute);
		}
	}
}

void TesselateParameter(SNurbs* element, boost::multi_array<nfloat, 2>& data, size_t k) {
	const std::pair<Vec2n, Vec2n>& domain = element->Domain();
	const Vec2n& uDomain = domain.first;
	const Vec2n& vDomain = domain.second;

	const size_t n = data.shape()[0];
	const size_t m = data.shape()[1];

	for(size_t i = 0; i < n; i++) {
		nfloat u0 = ((nfloat)i / (n - 1));
		nfloat u = 0.999999 * u0;
		u = u * (uDomain[1] - uDomain[0]) + uDomain[0];
		if(i == 0) u = uDomain[0] +  0.000001;
		for(size_t j = 0; j < m; j++) {
			nfloat v0 = ((nfloat)j / (m - 1));
			nfloat v = 0.999999 * v0;
			v = v * (vDomain[1] - vDomain[0]) + vDomain[0];
			if(j == 0) v = vDomain[0] + 0.000001;

			data[i][j] = (k == 0) ? u0 : v0;
		}
	}
}

void NurbsSurfaceRenderer::Tesselate(const std::string& attributeU, const std::string& attributeV) {
	for(size_t i = 0; i < element.size(); i++) {
		::Tesselate<Vec3n>(element[i], positionAttribute, position[i]);
		::TesselateNormal(element[i], normal[i], positionAttribute);

 		if(attributeU == "")
			::TesselateParameter(element[i], parameterU[i], 0);
		else
			::Tesselate<nfloat>(element[i], attributeU, parameterU[i]);

 		if(attributeV == "")
			::TesselateParameter(element[i], parameterV[i], 1);
		else
			::Tesselate<nfloat>(element[i], attributeV, parameterV[i]);

		const size_t n = position[i].shape()[0];
		const size_t m = position[i].shape()[1];
		for(size_t j = 0; j < n; j++) {
			for(size_t k = 0; k < n; k++) {
				Vec3n& P = position[i][j][k];
				Vec3n& N = normal[i][j][k];
				P = math::V3n(P[0], P[2], P[1]);
				N = math::V3n(N[0], N[2], N[1]);
			}
		}
	}
}

void NurbsSurfaceRenderer::RenderFilledElement(size_t k) const {
	const size_t n = position[k].shape()[0];
	const size_t m = position[k].shape()[1];

	for(size_t i = 0; i < n - 1; i++) {
		for(size_t j = 0; j < m - 1; j++) {
			glTexCoord2f(parameterU[k][i][j], parameterV[k][i][j]); glNormal3dv(normal[k][i][j].data); glVertex3dv(position[k][i][j].data);
			glTexCoord2f(parameterU[k][i][j + 1], parameterV[k][i][j + 1]); glNormal3dv(normal[k][i][j + 1].data); glVertex3dv(position[k][i][j + 1].data);
			glTexCoord2f(parameterU[k][i + 1][j + 1], parameterV[k][i + 1][j + 1]); glNormal3dv(normal[k][i + 1][j + 1].data); glVertex3dv(position[k][i + 1][j + 1].data);

			glTexCoord2f(parameterU[k][i][j], parameterV[k][i][j]); glNormal3dv(normal[k][i][j].data); glVertex3dv(position[k][i][j].data);
			glTexCoord2f(parameterU[k][i + 1][j + 1], parameterV[k][i + 1][j + 1]); glNormal3dv(normal[k][i + 1][j + 1].data); glVertex3dv(position[k][i + 1][j + 1].data);
			glTexCoord2f(parameterU[k][i + 1][j], parameterV[k][i + 1][j]); glNormal3dv(normal[k][i + 1][j].data); glVertex3dv(position[k][i + 1][j].data);
		}
	}
}

void NurbsSurfaceRenderer::RenderBoundaryElement(size_t k) const {
	const size_t n = position[k].shape()[0];
	const size_t m = position[k].shape()[1];

	for(size_t j = 0; j < m; j++) {
		glVertex3dv(position[k][0][j].data);
	}

	for(size_t i = 0; i < n; i++) {
		glVertex3dv(position[k][i][m - 1].data);
	}

	for(size_t j = m; j >= 1; j--) {
		glVertex3dv(position[k][n - 1][j - 1].data);
	}

	for(int i = n; i >= 1; i--) {
		glVertex3dv(position[k][i - 1][0].data);
	}
}

void NurbsSurfaceRenderer::Init() {
	std::cout << "Initializing" << std::endl;

	position.clear();
	normal.clear();
	parameterU.clear();
	parameterV.clear();
	const size_t epp = 10;
	for(size_t i = 0; i < element.size(); i++) {
		position.push_back(Array2V3n(boost::extents[epp][epp]));
		normal.push_back(Array2V3n(boost::extents[epp][epp]));
		parameterU.push_back(Array2(boost::extents[epp][epp]));
		parameterV.push_back(Array2(boost::extents[epp][epp]));
	}

	std::string attributeU = "";
	std::string attributeV = "";
	Tesselate(attributeU, attributeV);
}

void NurbsSurfaceRenderer::Update() {
	std::string attributeU = "";
	std::string attributeV = "";
	Tesselate(attributeU, attributeV);
}
	
void NurbsSurfaceRenderer::RenderGeometry() const {
	glPushAttrib(GL_ENABLE_BIT | GL_CURRENT_BIT | GL_POLYGON_BIT);
	glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	const Vec3n yellow = math::V3n(254.0 / 255.0, 237.0 / 255, 155.0 / 255.0);
	GLfloat mat_diffuse[4] = {yellow[0], yellow[1], yellow[2], 1.0};
	GLfloat mat_white[4] = {1.0, 1.0, 1.0, 1.0};
	glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
	glMaterialfv(GL_BACK, GL_DIFFUSE, mat_white);
	glPolygonOffset(1.0, 1.0);
	glEnable(GL_POLYGON_OFFSET_FILL);
	glBegin(GL_TRIANGLES);
	for(size_t i = 0; i < element.size(); i++) {
		RenderFilledElement(i);
	}
	glEnd();
	glDisable(GL_POLYGON_OFFSET_FILL);

	glDisable(GL_TEXTURE_2D);
	glDisable(GL_LIGHTING);
	glColor3f(0.0f, 0.0f, 0.0f);

	for(size_t i = 0; i < element.size(); i++) {
		glBegin(GL_LINE_STRIP);
		RenderBoundaryElement(i);
		glEnd();
	}

	glPopAttrib();
}

void NurbsSurfaceRenderer::Render() const {
	RenderGeometry();
}

NurbsSurfaceRenderer::~NurbsSurfaceRenderer() {

}
